package com.example.service;

import com.example.domain.info.ActionInfo;
import com.example.domain.info.PidInfo;
import com.example.domain.info.ResultMap;
import com.example.util.JsonUtils;
import com.example.util.SerialPortUtil;
import org.springframework.stereotype.Service;
import purejavacomm.*;

import javax.websocket.Session;
import javax.xml.bind.DatatypeConverter;
import java.math.BigInteger;
import java.util.*;

/**
 * 串口相关的服务
 */
@Service
public class SerialPortService {

    /**
     * 获取串口数据
     */
    private static final int GET_COM = 5;
    /**
     * 打开串口
     */
    private static final int OPEN_COM = 1;
    /**
     * 关闭串口
     */
    private static final int CLOSE_COM = -1;
    /**
     * 发送pid数据
     */
    private static final int SEND_PID = 10;
    /**
     * 读取pid数据
     */
    private static final int READ_PID = 11;
    /**
     * 保存pid数据
     */
    private static final int SAVE_PID = 12;
    /**
     * 自定义数据
     */
    private static final int MY_DATA = 17;

    /**
     * 保存串口的信息
     * 所以下线的时候记得关串口 不然得重启服务器
     */
    private static final Map<Session, SerialPort> serialPortMap = new HashMap<>();

    private static final Map<Byte, Byte> sumMap = new HashMap<>();

    private static final Map<Byte, Boolean> sumCheckMap = new HashMap<>();

    /**
     * 获取前端传来的信息 并判断相应的操作
     *
     * @param message
     * @param session
     * @return
     */
    public ResultMap receiveMessage(String message, Session session) throws InterruptedException {
        ResultMap requestMap = JsonUtils.resolveJson(message, ResultMap.class);
        Object code = requestMap.get(ResultMap.DEFAULT_CODE);
        if (!(code instanceof Integer)) {
            return ResultMap.getResult().put("msg", "未知操作");
        }
        switch ((Integer) code) {
            case GET_COM:
                return getSerialPorts();
            case OPEN_COM:
                return openSerialPorts(requestMap, session);
            case CLOSE_COM:
                return closeSerialPorts(session);
            case SEND_PID:
                return sendPid(requestMap, session);
            case READ_PID:
                return readPid(session);
            case SAVE_PID:
                return savePid(session);
            case MY_DATA:
                return sendMyData(requestMap, session);
            default:
                return ResultMap.getResult().put("msg", "未知操作");
        }
    }

    /**
     * 发送自定义的数据 约定是十六进制数据
     *
     * @param requestMap
     * @param session
     * @return
     */
    public ResultMap sendMyData(ResultMap requestMap, Session session) {
        String data = (String) requestMap.get("data");
        //发送数据
        try {
            SerialPortUtil.sendData(serialPortMap.get(session), DatatypeConverter.parseHexBinary(data));
        } catch (Exception e) {
            e.printStackTrace();
            return ResultMap.getResult().put("msg", "发送失败");
        }
        return ResultMap.getResult(data).put("msg", "发送成功");
    }

    /**
     * 保存pid
     *
     * @return
     */
    public ResultMap savePid(Session session) {
        if (SerialPortUtil.sendData(serialPortMap.get(session), new byte[]{(byte) 0xaa, (byte) 0xaf, (byte) 0x0d, 0,  (byte) 0x66}) <0 ){
            SerialPortUtil.closeSerialPort(serialPortMap.get(session));
            return ResultMap.getResult(null,ResultMap.SERIAL_PORT_ERROR).put("msg", "串口意外关闭");
        }
        return ResultMap.getResult(null,ResultMap.PID_REQUEST_SUCCESS).put("msg", "保存pid请求已发送");
    }

    /**
     * 读取pid请求
     *
     * @param session
     * @return
     */
    public ResultMap readPid(Session session) {
        if (SerialPortUtil.sendData(serialPortMap.get(session), new byte[]{(byte) 0xaa, (byte) 0xaf, (byte) 0x02, 1, 0x01, (byte) 0x5d})< 0 ){
            SerialPortUtil.closeSerialPort(serialPortMap.get(session));
            return ResultMap.getResult(null,ResultMap.SERIAL_PORT_ERROR).put("msg", "串口意外关闭");
        }
        return ResultMap.getResult(null,ResultMap.PID_REQUEST_SUCCESS).put("msg", "读取pid请求已发送");
    }

    /**
     * 前端发送pid数据给串口
     *
     * @return
     */
    public ResultMap sendPid(ResultMap requestMap, Session session) throws InterruptedException {
        //获取data数据
        List<List<Integer>> datas = (List<List<Integer>>) requestMap.get(ResultMap.DEFAULT_DATA);
        List<String> list = new ArrayList<>();
        sumMap.clear();
        sumCheckMap.clear();
        //由于pid数据三组为一帧，所以要依次遍历，一帧一帧的发送
        for (int i = 0; i < datas.size(); i++) {
            byte[] data = packPid(datas.get(i), i);
            list.add(DatatypeConverter.printHexBinary(data));
            //将数据放送给串口
            if (SerialPortUtil.sendData(serialPortMap.get(session), data) <0 ){
                SerialPortUtil.closeSerialPort(serialPortMap.get(session));
                return ResultMap.getResult(null,ResultMap.SERIAL_PORT_ERROR).put("msg", "串口意外关闭");
            }
            WebSocketService.sendMessage(JsonUtils.getJson(ResultMap.getResult(null,ResultMap.WAIT_PID_CHECK).put("msg","等待pid" + data[2] + "确认帧数据")), session);
            //存入校验和
            sumMap.put(data[2], data[data.length - 1]);
            System.out.println("确认帧"+Integer.toHexString(data[data.length - 1]));
            long begin = System.currentTimeMillis();
            int count = 1;
            //等待确认帧发送过来
            while (sumCheckMap.get(data[2]) == null) {
                //重传时间间隔 2s
                if (System.currentTimeMillis() - begin > 2000){
                    //最大重传次数
                    if (count > 5){
                        return ResultMap.getResult(null,ResultMap.PID_CHECK_ERROR).put("msg",data[2]+"pid确认帧超时");
                    }
                    //超时重传
                    if (SerialPortUtil.sendData(serialPortMap.get(session), data) <0 ){
                        SerialPortUtil.closeSerialPort(serialPortMap.get(session));
                        return ResultMap.getResult(null,ResultMap.SERIAL_PORT_ERROR).put("msg", "串口意外关闭");
                    }
                    WebSocketService.sendMessage(JsonUtils.getJson(ResultMap.getResult(null,ResultMap.WAIT_PID_CHECK).put("msg","pid" + data[2] + "确认帧重传第"+count+"次")), session);
                    //重置发送时间
                    begin = System.currentTimeMillis();
                    count++;
                }
                Thread.sleep(500);
            }
            //判断确认帧是否有效
            if (!sumCheckMap.get(data[2])){
                return ResultMap.getResult(null,ResultMap.PID_CHECK_ERROR).put("msg","确认帧不通过，停止发送");
            }
        }
        return ResultMap.getResult(list).put("msg", "pid写入成功");
    }


    /**
     * 封装pid数据
     *
     * @param data   数据包
     * @param number 帧序号
     * @return
     */
    private byte[] packPid(List<Integer> data, int number) {
        //封装pid数据
        int index = 0;
        byte[] pidData = new byte[23];
        //封装帧头
        pidData[index++] = (byte) 0xaa;
        pidData[index++] = (byte) 0xaf;
        pidData[index++] = (byte) ((byte) 0x10 + (byte) number);
        pidData[index++] = 18;
        //遍历数据
        for (Integer pid : data) {
            String s = Integer.toHexString(pid);
            //第二个字节
            String low = (s.length() - 2 >= 0 ? s.charAt(s.length() - 2) : '0') + "" + s.charAt(s.length() - 1);
            //第一个字节
            String high = (s.length() - 4 >= 0 ? s.charAt(s.length() - 4) : '0') + "" + (s.length() - 3 >= 0 ? s.charAt(s.length() - 3) : '0');
            pidData[index++] = (byte) Integer.parseInt(high, 16);
            pidData[index++] = (byte) Integer.parseInt(low, 16);
        }
        //获取校验和
        byte sum = 0;
        for (byte pidDatum : pidData) {
            sum += pidDatum;
        }
        pidData[index] = sum;
        return pidData;
    }

    /**
     * 打开相应的串口
     *
     * @param requestMap
     * @param session
     * @return
     */
    public ResultMap openSerialPorts(ResultMap requestMap, Session session) {
        //获取串口的信息
        Object comName = requestMap.get("COM");
        try {
            //打开串口
            SerialPort serialPort = SerialPortUtil.openSerialPort((String) comName, 500000);
            //建立串口与session得映射关系
            serialPortMap.put(session, serialPort);
            //监听串口
            SerialPortUtil.setListenerToSerialPort(serialPort, event -> {
                try {
                    //获取数据
                    if (event.getEventType() == SerialPortEvent.DATA_AVAILABLE) {
                        //传送的原始字节数据
                        byte[] data = SerialPortUtil.readData(serialPort);
                        for (byte datum : data) {
                            System.out.print(Integer.toHexString(datum & 0x00ff) + "-");
                        }
                        System.out.println();
                        System.out.println("===============");
                        //解析数据
                        ResultMap resultMap = resolveData(data);

                        if (resultMap != null) {
                            //将数据发送到前端
                            WebSocketService.sendMessage(JsonUtils.getJson(resultMap), session);
                        }

                    }
                } catch (Exception e) {
                    System.out.println("=========出现异常=========");
                    e.printStackTrace();
                    //出现异常关闭串口
//                    closeSerialPorts(session);
//                    System.out.println("异常关闭串口成功");
                }

            });
            return ResultMap.getResult().put("msg", "串口开启成功");
        } catch (Exception e) {
            e.printStackTrace();
            return ResultMap.getResult().put("msg", "串口开启失败");
        }
    }

    /**
     * 关闭串口操作
     *
     * @param session
     * @return
     */
    private ResultMap closeSerialPorts(Session session) {
        if (SerialPortUtil.closeSerialPort(serialPortMap.get(session))) {
            serialPortMap.remove(session);
            return ResultMap.getResult().put("msg", "串口关闭成功");
        }
        return ResultMap.getResult().put("msg", "串口关闭失败");
    }

    /**
     * 解析串口传送过来的数据
     *
     * @param data
     */
    private ResultMap resolveData(byte[] data) {
        //校验帧头
        if (data.length < 4 || data[0] != (byte) 0xaa || data[1] != (byte) 0xaa) {
            return ResultMap.getResult(byteArrayToHex(data),ResultMap.UN_KNOW).put("msg", "帧头未知").put("isLess", true);
            //return null;
        } else if (data[2] == (byte) 0x01) {
            //为飞机姿态数据
            return resolveActionInfo(data);
            //return null;
        } else if (data[2] >= (byte) 0x10 && data[2] <= (byte) 0x15) {
            //为飞机PID数据
            return resolvePidInfo(data);
        } else if (data[2] == (byte) 0xef) {
            if(data[4] == (byte) 0x0d){
                return ResultMap.getResult(byteArrayToHex(data),ResultMap.PID_CHECK_SUCCESS).put("msg", "保存确认帧通过").put("isLess", true);
            }
            //pid确认帧
            ResultMap resultMap;
            //应该是7
            if (data.length < 6) {
                return ResultMap.getResult(byteArrayToHex(data)).put("msg", "确认帧位数不足").put("isLess", true);
            }
            //判断校验和是否正确
            if (sumMap.get(data[4]) == null || sumMap.get(data[4]) != data[5]) {
                resultMap = ResultMap.getResult(byteArrayToHex(data), ResultMap.PID_CHECK_ERROR).put("msg", DatatypeConverter.printByte(data[4]) + "确认帧不通过(校验和错误)");
                sumCheckMap.put(data[4], false);
            } else {
                resultMap = ResultMap.getResult(byteArrayToHex(data), ResultMap.PID_CHECK_SUCCESS).put("msg", DatatypeConverter.printByte(data[4]) + "确认帧通过");
                sumCheckMap.put(data[4], true);
            }
            return resultMap;

        } else {
            return ResultMap.getResult(byteArrayToHex(data),ResultMap.UN_KNOW).put("msg", "未知").put("isLess", true);
            //return null;
        }

    }

    /**
     * 解析飞机姿态数据
     *
     * @param data
     * @return
     */
    private ResultMap resolveActionInfo(byte[] data) {
        //从第4个字节开始往后，记录了数据长度和数据本身以及校验位
        int i = 3;
        ActionInfo actionInfo = new ActionInfo();
        //传送数据的字节数
        actionInfo.setContentLength(data[i++]);
        // 4 为前面帧头+功能字+长度 1为最后一个校验位 加上数据长度后判断是否和传递的字节数组长度相等 否则判断为缺少传递数据
        if (4 + actionInfo.getContentLength() + 1 != data.length) {
            return ResultMap.getResult().put("msg", "姿态传递数据缺失").put("isLess", true);
        }
        //根据文档进行数据的解析
        //横滚角
        actionInfo.setRol((short) (((data[i++] << 8) + data[i++]) / 100));
        //俯仰角
        actionInfo.setPit((short) (((data[i++] << 8) + data[i++]) / 100));
        //偏航角
        int j = i;
        actionInfo.setYam((short) (((data[i++] << 8) + data[i++]) / 100));
        actionInfo.setHeight((data[i++] << 24) + (data[i++] << 16) + (data[i++] << 8) + data[i++]);
        actionInfo.setMode(data[i++]);
        actionInfo.setLock(data[i]);
        return ResultMap.getResult(actionInfo, ResultMap.ACTION_CODE).put("msg", "姿态解析完毕").put("isLess", false);
    }

    /**
     * 将字节数组变为十六进制字符串
     *
     * @param bytes
     * @return
     */
    private static String byteArrayToHex(byte[] bytes) {
        if (bytes.length == 0) {
            return null;
        }
        BigInteger bigInt = new BigInteger(1, bytes);
        String hex = bigInt.toString(16);
        int paddingLength = (bytes.length * 2) - hex.length();
        if (paddingLength > 0) {
            return String.format("%0" + paddingLength + "d", 0) + hex;
        } else {
            return hex;
        }
    }

    /**
     * 解析pid数据
     *
     * @param data
     * @return
     */
    private ResultMap resolvePidInfo(byte[] data) {
        //从第4个字节开始往后，记录了数据长度和数据本身以及校验位
        int i = 2;
        //记录下功能字
        int word = data[i++];
        // 4 为前面帧头+功能字+长度 1为最后一个校验位 加上数据长度后判断是否和传递的字节数组长度相等 否则判断为缺少传递数据
        if (4 + data[i++] + 1 != data.length) {
            return ResultMap.getResult().put("msg", "pid传递数据缺失").put("isLess", true);
        }
        //下标
        int index = word % 16 * 3;
        Map<String, PidInfo> result = new HashMap<>();
        //对功能字进行处理 用作json的键
        for (int t = 1; t <= 3; t++) {
            PidInfo pidInfo = new PidInfo();
            pidInfo.setP((short) ((data[i++] << 8) + data[i++]));
            pidInfo.setI((short) ((data[i++] << 8) + data[i++]));
            pidInfo.setD((short) ((data[i++] << 8) + data[i++]));
            //System.out.println(pidInfo);
            result.put(Integer.toString(index + t), pidInfo);
        }
        return ResultMap.getResult(result, ResultMap.PID_CODE).put("msg", "pid解析完毕").put("isLess", false);
    }


    /**
     * 获取所有的串口数据
     *
     * @return
     */
    public ResultMap getSerialPorts() {
        return ResultMap.getResult(SerialPortUtil.getSerialPortList()).put(ResultMap.DEFAULT_CODE, 0);
    }

}
